/*
 * forms.js
 * Written by Joshua M. Thompson
 *
 * Helper functions for creating rich forms using the LL custom tags library and ExtJS
 */

/*jslint browser: true, devel: true, eqeqeq: false, onevar: false, plusplus: false, white: false */
/*global Ext: true, LINGUIST: true, OpenLayers: true, window: true */

Ext.define('LINGUIST.Form', {
    statics: {
        forms: [],

        findByName: function (name) {
            var f;

            for (var i = 0 ; i < LINGUIST.Form.forms.length ; i++) {
                f = LINGUIST.Form.forms[i];

                if (f.name == name) {
                    return f;
                }
            }
        },

        registerForm: function (form) {
            LINGUIST.Form.forms.push(form);
        }
    },

    constructor: function (name, fields, constraints) {
        this.form        = Ext.get(document.getElementsByName(name)[0]);
        this.name        = name;
        this.fields      = fields;
        this.constraints = constraints;

        LINGUIST.Form.registerForm(this);

        for (var i = 0 ; i < this.fields.length ; i++) {
            this.createField(this.fields[i]);
        }

        var els = this.form.select("textarea[data-showlength]");
        var disp;
        var max;

        els.each(function (el) {
            disp = Ext.get(el.getAttribute('data-showlength'));
            max  = el.getAttribute('maxlength');

            // The Ext.get(...) is so that each function object gets a unique element object
            // instead of the (same) shared flyweight Ext.dom.Element, which causes problems
            // when there are multiple char counts on one page.
            el.on('keyup', Ext.Function.pass(this.showCharCount, [ Ext.get(el.dom), disp, max ], true));

            this.showCharCount(el, disp, max);
        }, this);
    },

    createField: function (field) {
        if (field.type == 'datepicker') {
            Ext.create('Ext.form.field.Date', {
                id: field.id,
                name: field.name,
                renderTo: this.getContainer(field),
                editable: false,
                format: 'Y-m-d',
                width: field.width,
                value: field.value
            });
        }
        else if (field.type == 'multifamily') {
            this.createMultiFA(field);
        }
        else if (field.type == 'multijobrank') {
            this.createMultiJobRank(field);
        }
        else if (field.type == 'multilanguage') {
            this.createMultiSL(field);
        }
        else if (field.type == 'multilingfield') {
            this.createMultiLF(field);
        }
    },

    /*
     * For fields using a popup window (e.g. the multi* field types), create
     * the a standard DOM structure with a display field and hidden input fields
     * for storing the ids and names of the selected records
     */

    createPopupFields: function (field) {
        field.el = this.getContainer(field);

        field.displayEl = Ext.DomHelper.append(field.el, {
            tag: 'div',
            name: field.name + '_display',
            html: ''
        }, true);

        field.namesEl = Ext.DomHelper.append(field.el, {
            tag: 'input',
            type: 'hidden',
            name: field.name + '_names',
            value: ''
        }, true);

        field.valuesEl = Ext.DomHelper.append(field.el, {
            tag: 'input',
            type: 'hidden',
            name: field.name,
            value: ''
        }, true);
    },

    getContainer: function (field) {
        return Ext.get(field.name + '_container');
    },

    updatePopupFields: function (field, ids, names) {
        field.namesEl.dom.value  = names.join('; ');
        field.valuesEl.dom.value = ids.join(',');

        if (field.valuesEl.dom.onchange) {
            field.valuesEl.dom.onchange();
        }

        if (names.length) {
            field.displayEl.update(field.namesEl.dom.value);
        }
        else {
            field.displayEl.update('<span style="font-style: italic;">None selected</span>');
        }
    },

    /*-------------------------------------------*
     * Methods that apply to multijobrank fields *
     *-------------------------------------------*/

    createMultiJobRank: function (field) {
        this.createPopupFields(field);

        field.store = Ext.create('Ext.data.Store', {
            autoLoad: false,
            model: 'LINGUIST.job.Rank',
            data: [],
            sorters: [ 'rank' ]
        });

        field.b1 = Ext.DomHelper.append(field.el, {
            tag: 'button',
            type: 'button',
            html: 'Add/Change Job Ranks'
        }, true);

        field.b2 = Ext.DomHelper.append(field.el, {
            tag: 'button',
            type: 'button',
            html: 'Clear Job Ranks'
        }, true);

        var updater = Ext.Function.pass(this.updateMultiJRList, [ field ], this);

        field.store.on('clear', updater, this);
        field.store.on('datachanged', updater, this);

        field.b1.on('click', Ext.Function.pass(this.editMultiJRList, [ field ], this));
        field.b2.on('click', Ext.Function.pass(this.clearMultiJRList, [ field ], this));

        field.store.loadData(field.data);
    },

    clearMultiJRList: function (field) {
        field.store.removeAll();
    },

    editMultiJRList: function (field) {
        var w = Ext.create('LINGUIST.popup.MultiJobRank', {
            title: 'Select Job Ranks',
            store: field.store
        });

        w.show();
    },

    updateMultiJRList: function (field) {
        var ids    = [];
        var labels = [];

        field.store.each(function(r) {
            ids.push(r.get('rankid'));
            labels.push(r.get('rank'));
        });

        this.updatePopupFields(field, ids, labels);
    },

    /*
     * Methods that apply to LF (Linguistic Field) fields
     */

    createMultiLF: function (field) {
        this.createPopupFields(field);

        field.store = Ext.create('Ext.data.Store', {
            autoLoad: false,
            model: 'LINGUIST.LinguisticField',
            data: [],
            sorters: [ 'primary_name' ]
        });

        field.b1 = Ext.DomHelper.append(field.el, {
            tag: 'button',
            type: 'button',
            html: 'Add/Change Linguistic Fields'
        }, true);

        field.b2 = Ext.DomHelper.append(field.el, {
            tag: 'button',
            type: 'button',
            html: 'Clear Linguistic Fields'
        }, true);

        var updater = Ext.Function.pass(this.updateMultiSCList, [ field ], this);

        field.store.on('clear', updater);
        field.store.on('datachanged', updater);

        field.b1.on('click', Ext.Function.pass(this.editMultiLFList, [ field ], this));
        field.b2.on('click', Ext.Function.pass(this.clearMultiSCList, [ field ], this));

        field.store.loadData(field.data);
    },

    editMultiLFList: function (field) {
        var w = Ext.create('LINGUIST.popup.MultiLingField', {
            title: 'Select Ling Fields',
            store: field.store,
            maxSelection: field.maxselection === ''? null : field.maxselection
        });

        w.show();
    },

    /*
     * Methods that apply to SL (Subject Language) fields
     */

    createMultiSL: function (field) {
        this.createPopupFields(field);

        field.store = Ext.create('Ext.data.Store', {
            autoLoad: false,
            model: 'LINGUIST.LanguageCode',
            data: [],
            sorters: [ 'primary_name' ]
        });

        field.b1 = Ext.DomHelper.append(field.el, {
            tag: 'button',
            type: 'button',
            html: 'Add/Change Languages'
        }, true);

        field.b2 = Ext.DomHelper.append(field.el, {
            tag: 'button',
            type: 'button',
            html: 'Clear Languages'
        }, true);

        var updater = Ext.Function.pass(this.updateMultiSCList, [ field ], this);

        field.store.on('clear', updater);
        field.store.on('datachanged', updater);

        field.b1.on('click', Ext.Function.pass(this.editMultiSLList, [ field ], this));
        field.b2.on('click', Ext.Function.pass(this.clearMultiSCList, [ field ], this));

        field.store.loadData(field.data);
    },

    editMultiSLList: function (field) {
        var w = Ext.create('LINGUIST.popup.MultiLanguage', {
            title: 'Select Languages',
            store: field.store,
            classType: 'SL'
        });

        w.show();
    },

    /*
     * Methods that apply to FA (Language Family) fields
     */

    createMultiFA: function (field) {
        this.createPopupFields(field);

        field.store = Ext.create('Ext.data.Store', {
            autoLoad: false,
            model: 'LINGUIST.LanguageCode',
            data: [],
            sorters: [ 'primary_name' ]
        });

        field.b1 = Ext.DomHelper.append(field.el, {
            tag: 'button',
            type: 'button',
            html: 'Add/Change Families'
        }, true);

        field.b2 = Ext.DomHelper.append(field.el, {
            tag: 'button',
            type: 'button',
            html: 'Clear Families'
        }, true);

        var updater = Ext.Function.pass(this.updateMultiSCList, [ field ], this);

        field.store.on('clear', updater);
        field.store.on('datachanged', updater);

        field.b1.on('click', Ext.Function.pass(this.editMultiFAList, [ field ], this));
        field.b2.on('click', Ext.Function.pass(this.clearMultiSCList, [ field ], this));

        field.store.loadData(field.data);
    },

    editMultiFAList: function (field) {
        var w = Ext.create('LINGUIST.popup.MultiLanguage', {
            title: 'Select Families',
            store: field.store,
            classType: 'FA'
        });

        w.show();
    },

    /*
     * Methods that apply to any form field based on subject classifications (LF, SL or FA fields)
     */

    clearMultiSCList: function (field) {
        field.store.removeAll();
    },

    updateMultiSCList: function (field) {
        var ids    = [];
        var labels = [];

        field.store.each(function(r) {
            ids.push(r.get('subclassid'));
            labels.push(r.get('primary_name'));
        });

        this.updatePopupFields(field, ids, labels);
    },

    showCharCount: function (el, displayEl, max) {
        var count = el.dom.value.length;
        var rem   = max - count;
        var ess   = rem == 1? '': 's';
        var cls   = (max !== null) && (rem < 20)? 'text-length-bad' : 'text-length-good';

        if (rem < 0) {
            rem = 0;
        }

        var msg = '<span class="' + cls + '">' + count + ' character' + ess;

        if (max > 0) {
            msg = msg + ' (' + rem + ' remaining)';
        }

        msg = msg + '</span>';

        displayEl.update(msg);
    },

    validateRequireGroup: function (form, constraint, errors) {
        var fields = constraint.fields.split(',');
        var valid  = false;
        var el;

        for (var i = 0 ; i < fields.length ; i++) {
            el = form[fields[i]];

            if (el.nodeName == 'SELECT') {
                for (var j = 0 ; j < el.options.length ; j++) {
                    if ((el.options[j].value !== '') && el.options[j].selected) {
                        valid = true;
                        break;
                    }
                }
            }
            else if (el.nodeName == 'INPUT') {
                if ((el.type == 'checkbox') && el.checked) {
                    valid = true;
                }
                else if (((el.type == 'text') || (el.type == 'hidden')) && (el.value.trim() !== '')) {
                    valid = true;
                }
            }
        }

        if (!valid) {
            errors.push(constraint.title);
        }
    },

    validateSelectField: function (field, errors) {
        if (field.hasAttribute('required')) {
            if (field.selectedIndex == -1) {
                errors.push(field.title);
            }
            else if (field.options[field.selectedIndex].value === '') {
                errors.push(field.title);
            }
        }
    },

    validateTextField: function (field, errors) {
        var pattern;

        if (field.hasAttribute('pattern')) {
            pattern = new RegExp(field.getAttribute('pattern'));
        }
        else if (field.type == 'email') {
            pattern = /^[a-zA-Z0-9.!#$%&'*+\/=?\^_`{|}~\-]+@[a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)+$/;
        }
        else if (field.type == 'url') {
            // regex is from the h5validator jQuery plugin
            pattern = /(https?|ftp):\/\/(((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?/;
        }

        if (field.value === '') {
            if (field.hasAttribute('required')) {
                errors.push(field.title);

                return;
            }
        }
        else {
            if (pattern && !pattern.test(field.value)) {
                errors.push(field.title);

                return;
            }
        }

        if (field.hasAttribute('maxlength') && (field.value.length > field.getAttribute('maxlength'))) {
                errors.push(field.title);
        }
    },

    validate: function () {
        var errors = [];
        var form   = document.getElementsByName(this.name)[0];
        var field,type,c;

        for (var i = 0 ; i < form.length ; i++) {
            field = form[i];

            if (!field.disabled) {
                if (field.nodeName == 'INPUT') {
                    type = field.type;

                    if ((type == 'text') || (type == 'hidden') || (type == 'email') || (type == 'url')) {
                        this.validateTextField(field, errors);
                    }
                }
                else if (field.nodeName == 'SELECT') {
                    this.validateSelectField(field, errors);
                }
                else if (field.nodeName == 'TEXTAREA') {
                    this.validateTextField(field, errors);
                }
            }
        }

        for (i = 0 ; i < this.constraints.length ; i++) {
            c = this.constraints[i];

            if (c.type == 'requiregroup') {
                this.validateRequireGroup(form, c, errors);
            }
        }

		
        if (errors.length) {
            var msg;
            if (errors.length == 1) {
                msg = 'An error was detected in your form submission:';
            }
            else {
                msg = errors.length + ' errors were detected in your form submission:';
            }

            msg = msg + '<br><br><ul class="form-errors">';

            for (i = 0 ; i < errors.length ; i++) {
                msg = msg + '<li>' + errors[i] + '</li>';
            }
            msg = msg + '</ul><br>';

            if (errors.length == 1) {
                msg = msg + 'Please correct the error and try again.';
            }
            else {
                msg = msg + 'Please correct the errors and try again.';
            }

            Ext.Msg.show({
                icon: Ext.MessageBox.ERROR,
                buttons: Ext.MessageBox.OK,
                closable: false,
                width: 400,
                title: 'Validation Failure',
                msg: msg,
                scope: this,
                fn: function () {
                }
            });

            return false;
        }
        else {
            return true;
        }
    }
});
